#include <arch.h>
#include <sys/kmalloc.h>
#include <conio.h>
#include <paging.h>
#include <multiboot.h>
#include <mman.h>

/* pools of system memory: base & high */
static struct pages_stack_type low_memory, all_memory;

extern uint32_t __end;
extern uint32_t __bss;
extern uint32_t __data;
extern uint32_t __code;
extern uint32_t image_size;
extern sysinfo_t sys;

uint32_t alloc_mempool(struct pages_stack_type *pool)
{
    uint32_t addr;

    do {
	if (pool->free_pages <= 0)
	    return 0;

	addr = pool->pages[--pool->free_pages];
	if ((addr != 0) && (addr != 1)) {
	    pool->pages[pool->free_pages] = 0;

	    return (addr);
	}
    } while (addr != -1);

    return 0;
}

uint32_t alloc_mempools(struct pages_stack_type * pool, uint32_t npages)
{
    uint32_t addr, tmp;
    int i;

    // check for some errors
    if ((pool->free_pages - npages) <= 0)
	return 0;

    do {
	addr = pool->pages[--pool->free_pages];

	if (addr != 0) {
	    i = pool->free_pages;

	    do {
		tmp = pool->pages[--i];
		//kprintf("1st at 0x%lx, next page at 0x%lx\n",addr,tmp);
		pool->pages[i] = 0;
	    } while (i > (pool->free_pages - npages));
	    pool->free_pages = i;
	    addr = tmp;

	    return (addr);
	}
    } while (addr != -1);

    return 0;
}

uint32_t lock_mempool(struct pages_stack_type * pool, uint32_t start,
		      uint32_t end)
{
    if (pool->free_pages <= 0)
	return 0;

    start = PAGE_ALIGN(start);
    end = PAGE_ALIGN(end);

    for (; start < end; start += PAGE_SIZE)
	pool->pages[pool->free_pages--] = 1;

    return (end);
}


uint32_t lock_lowpages(uint32_t start, uint32_t end)
{
    return (lock_mempool(&low_memory, start, end));
}

uint32_t lock_pages(uint32_t start, uint32_t end)
{
    return (lock_mempool(&all_memory, start, end));
}

void free_mempool(struct pages_stack_type *pool, uint32_t block)
{
    pool->pages[pool->free_pages++] = block;
}

void free_mempoolrange(struct pages_stack_type *pool, uint32_t start,
		       uint32_t end)
{
    start = PAGE_ALIGN(start);
    end = PAGE_ALIGN(end);

    for (; start < end; start += PAGE_SIZE)
	pool->pages[pool->free_pages++] = start;
}

uint32_t alloc_page()
{
    return (alloc_mempool(&all_memory));
}

uint32_t alloc_lowpage()
{
    return (alloc_mempool(&low_memory));
}

uint32_t alloc_pages(uint32_t npages)
{
    return (alloc_mempools(&all_memory, npages));
}

uint32_t alloc_lowpages(uint32_t npages)
{
    return (alloc_mempools(&low_memory, npages));
}

void free_page(uint32_t block)
{
    free_mempool(&all_memory, block);
}

void free_lowpage(uint32_t block)
{
    free_mempool(&low_memory, block);
}

void free_pages(uint32_t start, uint32_t end)
{
    free_mempoolrange(&all_memory, start, end);
}

void free_lowpages(uint32_t start, uint32_t end)
{
    free_mempoolrange(&low_memory, start, end);
}

/* todo: change to MEMORY SMAP from grub2! */
void mman_init(void)
{
    uint32_t mem_size = 0;
    uint32_t free_mem_region = 0;
    multiboot_info_t *info = arch_getmbi();
    mem_size += (info->mem_lower + info->mem_upper);

    kprintf("init memory manager...\n");
    kprintf("base memory size: %lu Kb\n", info->mem_lower);
    kprintf("extended memory size: %lu Kb\n", info->mem_upper);

    kprintf("image_size=0x%x bytes\n", image_size);
    low_memory.pages = (uint32_t *) 0x100000 + image_size;
    kprintf("stack of low pages at 0x%p\n", low_memory.pages);
    all_memory.pages = (uint32_t *) low_memory.pages + 0x400;
    kprintf("stack of high pages at 0x%p\n", all_memory.pages);

    low_memory.npages = NUM_LOW_PAGES;
    all_memory.npages = mem_size * 0x400 / PAGE_SIZE - NUM_LOW_PAGES;
    kprintf("num of pages in low memory: %lu\n", low_memory.npages);
    kprintf("num of pages in high memory: %lu\n", all_memory.npages);
    kprintf("size of high mem stack: %lu bytes\n", all_memory.npages * 4);

    free_mem_region = (uint32_t) all_memory.pages + (all_memory.npages * 4);
    free_mem_region = PAGE_ALIGN_UP(free_mem_region);
    kprintf("first mem free block at 0x%p\n", free_mem_region);
    low_memory.free_pages = 0;
    all_memory.free_pages = 0;

    free_lowpages(0, BASE_MEMORY);	// 0 - 640 Kb, 384 Kb reserved!
    free_pages(free_mem_region, mem_size * 1024);	/* at (free_mem_region) mark */
    kprintf("free low pages: %lu\n", low_memory.free_pages);
    kprintf("free high pages: %lu\n", all_memory.free_pages);

    paging_init();
    sys.free_mem_region = free_mem_region;
    sys.mem_size = mem_size;
    kmalloc_init(free_mem_region, mem_size * 0x400);
}
